package de.gameduell.net.src.communication {
	import de.gameduell.net.src.connection.Synchronisation;	

	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.system.Security;
	import flash.utils.Timer;

	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.util.FlashVars;
	import de.gameduell.framework.util.LockManager;
	import de.gameduell.net.src.communication.CommunicationEvent;
	import de.gameduell.net.src.communication.SyncEvent;
	import de.gameduell.net.src.connection.IConnection;
	import de.gameduell.net.src.connection.PollConnection;
	import de.gameduell.net.src.connection.SocketConnection;
	import de.gameduell.net.src.message.IMessage;
	import de.gameduell.net.src.message.MessageEvent;
	import de.gameduell.net.src.protocol.CommunicationRequests;
	import de.gameduell.net.src.protocol.IProtocol;
	import de.gameduell.net.src.protocol.Protocol_V2;	

	public class MetaGameCommunication extends BaseCommunication implements ICommunication {

		public const PROTOCOL_V2:IProtocol = new Protocol_V2();
		public static const DEBUG_ENABLED:Boolean = true;
		public static const SOCKET_CONNECTION:String = "socket";
		public static const LOADVARS_CONNECTION:String = "loadvars";
		private const RECONNECT_TIMER:int = 8000;
		private const MAX_RECONNECTS:int = 5;
		private const PORT:int = 8042;
		private var connection:IConnection;
		private var confirmationQueue:Array;
		private var lockManager:LockManager;
		private var reconnectTimer:Timer;
		private var reconnectCount:int;
		private var host:String;
		private var connectionClosed:Boolean = false;

		public function MetaGameCommunication() {
			super("MetaGameCommunication");
			host = FlashVars.getKeyValue("gchost");
			reconnectCount = 0;
			
			messageQueue = new MessageQueue();		
//			messageQueue.addEventListener(MessageEvent.MESSAGE_EXPIRED, onMessageExpired);
//			messageQueue.addEventListener(MessageEvent.MAX_RETRIES_REACHED, disconnect);
			messageQueue.addEventListener(MessageEvent.NEXT_MESSAGE, onNextMessage);
			addEventListener(CommunicationEvent.DISCONNECT, disconnect);
			
			// messages sended to the server which have to be acked
			confirmationQueue = new Array();
			
			reconnectTimer = new Timer(RECONNECT_TIMER);
			reconnectTimer.addEventListener(TimerEvent.TIMER, onReconnectTimer);
		
			lockManager = LockManager.getInstance();
		}

		public function connect():void {
			log("timeout polltime: " + settings.pollSync, Debug.CONNECTION);
			connection = new PollConnection(this, settings.pollSync);
			registerConnectionEvents();
			connection.connect(host, PORT);
		}

		private function registerConnectionEvents():void {
			connection.addEventListener(DataEvent.DATA, receive);
			connection.addEventListener(Event.CLOSE, close);
			connection.addEventListener(IOErrorEvent.IO_ERROR, ioError);
			connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityError);
			connection.addEventListener(CommunicationEvent.CONNECTED, connected);
			connection.addEventListener(CommunicationEvent.TIMEOUT, timeout);
		}

		private function unregisterConnectionEvents():void {
			connection.removeEventListener(DataEvent.DATA, receive);
			connection.removeEventListener(Event.CLOSE, close);
			connection.removeEventListener(IOErrorEvent.IO_ERROR, ioError);
			// not removed because the socketconnection will throw a security error if no connection is available
			//          connection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityError); 
			connection.removeEventListener(CommunicationEvent.CONNECTED, connected);
			connection.removeEventListener(CommunicationEvent.TIMEOUT, timeout);
		}

		/**
		 * Create a message and push it to the queue of unconfirmed messages.
		 * The first message of the queue holds a timer which fires a message expired event.
		 * 
		 * Finally send the message via the give connection.
		 * @param requestType		a message type (e.g. ".sync", "init", etc.)
		 * @param parameters		message content 
		 */
		public function send(requestType:String, parameters:Array):IMessage {
			Debug.trace("send  " + requestType +" "+parameters, Debug.DEBUG);	
			var message:IMessage = protocol.createMessage(requestType, parameters, messageQueue.nextMessageId);		
			if (requestType != CommunicationRequests.SYNC) {
				log("send  " + requestType +" "+message.content);	
				lockManager.lock(this);
				messageQueue.addMessage(message, connection.getType());
				lockManager.unlock(this);
			}
			return message;
		}

		private function onNextMessage(event:MessageEvent):void {
			connection.sendMessage(event.message);
		}

		private function securityError(event:SecurityErrorEvent):void {
			Debug.trace("Communication::securityError: " + event, Debug.WARNING);
		}

		private function timeout(event:CommunicationEvent = null):void {
			log("timeout " + event, Debug.CONNECTION);
			if(connection.used) {
				reconnectTimer.stop();
				reconnect();
			} else {
				Debug.trace("Communication::timeout: Could not create PollConnection", Debug.ERROR);
			}
		}

		private function close(event:Event):void {
			log("closeHandler: " + event, Debug.CONNECTION);
			if(!connectionClosed) {
				log("closeHandler: reconnecting ..." + event, Debug.WARNING);
				reconnect();
			}
		}

		private function ioError(event:IOErrorEvent):void {
			log("ioErrorHandler: " + event, Debug.ERROR);
		}

		private function disconnect(event:Event):void {
			log("disconnect ... !!! DISCONNECTED !!!", Debug.ERROR);
			reconnectTimer.stop();
			removeEventListener(CommunicationEvent.DISCONNECT, disconnect);
			dispatchEvent(new CommunicationEvent(CommunicationEvent.DISCONNECT));
		}

		private function reconnect():void {	
			log("reconnect ...", Debug.ERROR);
			if(reconnectCount <= MAX_RECONNECTS) {
				dispatchEvent(new CommunicationEvent(CommunicationEvent.RECONNECT));
				addEventListener(CommunicationEvent.CONNECTED, reconnected);
				messageQueue.reconnecting = true;
				reconnectTimer.start();
				reconnectCount++;
				try {
					connection.reconnect(host, PORT);
				}catch(e:Error) {
					log("reconnect: error: " + e, Debug.ERROR);
				}
			} else {
				dispatchEvent(new CommunicationEvent(CommunicationEvent.DISCONNECT));
			}
		}

		private function onReconnectTimer(event:TimerEvent):void {
			log("onReconnectTimer ...", Debug.ERROR);
			reconnectCount++;
			if(reconnectCount > MAX_RECONNECTS) {
				dispatchEvent(new CommunicationEvent(CommunicationEvent.DISCONNECT));
			} else {
				reconnect();
			}
		}

		private function onMessageExpired(event:MessageEvent):void {
			log("onMessageExpired: " + event.message.requestType, Debug.ERROR);
			if(event.message.requestType == CommunicationRequests.SOCKET) {
				messageQueue.shift();
				timeout();
			} else {
				reconnect();
			}
		}

		private function reconnected(event:CommunicationEvent):void {
			log("reconnected ..." + event, Debug.CONNECTION);
			reconnectTimer.stop();
			reconnectCount = 0;
			dispatchEvent(new CommunicationEvent(CommunicationEvent.RECONNECTED));
		}

		public function connected(event:CommunicationEvent):void {
			log("connected", Debug.CONNECTION);
			dispatchEvent(new CommunicationEvent(CommunicationEvent.CONNECTED));
		}	

		private function receive(event:DataEvent):void {
			try {
				var serverMessages:Array = protocol.extractMessages(event.data);
				event.data = "";		
			}catch(a:Error) {
				log("receive  " + a, Debug.ERROR);	
			}
			var firstMessageId:int = IMessage(serverMessages[0]).id;
			var attchedMessages:Boolean = (serverMessages.length > 1);
			
			var isUnhandledMessage:Boolean = Synchronisation.getInstance().onReceiveMessage(new SyncEvent(SyncEvent.RECEIVE, firstMessageId, attchedMessages));
			if(isUnhandledMessage) {
				try {
					for(var n:int = 0;n < serverMessages.length; n++) {
						var serverMessage:IMessage = IMessage(serverMessages[n]);
						log("receive response: " + serverMessage.content, Debug.RECEIVE);			
						checkMessage(serverMessage);
					}
				}catch(ex:Error) {
					log("receive  error: " + ex, Debug.ERROR);	
				}
			}
		}

		public function checkMessage(serverMessage:IMessage):void {
			dispatchEvent(new ServerMessageEvent(serverMessage.requestType, serverMessage.parameters));
//			NO NEED fot this since there is no error yet, and no ACK
//			switch(serverMessage.requestType) {
//				case CommunicationRequests.ERROR:
//					log("checkMessage: ERROR! " + serverMessage.content,Debug.ERROR);
//					break;
//				default:
//					dispatchEvent(new ServerMessageEvent(serverMessage.requestType, serverMessage.parameters));
//			}
		}

		public function terminate():void {
			connectionClosed = true;
			unregisterConnectionEvents();
			messageQueue.close();
			connection.close();
		}

		private function log(aMessage:String, logMode:String = Debug.DEBUG):void {
			if(DEBUG_ENABLED || logMode == Debug.ERROR) {
				Debug.trace("MetaGameCommunication::" + aMessage, logMode);
			}
		}
	}
}
